/*
 * ﻿Copyright (C) 2013-2014 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.eatestutil.container;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;

import com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl.EjbContextCloseTask;

/**
 * Aspect to manage synchronization lock between <code>{@link EjbContextCloseHandlerImpl}</code> and 
 * <code>{@link EjbContextCloseTask}</code> for closing operation of EJB context. <br />
 * 
 * @author Arata Y.
 */
@Aspect 
class EjbContextCloseLockAspect {
	 /* Policy of choosing point-cut here is to include <code>{@link EjbContextCloseHandlerImpl#cancel()}</code> 
	 * method, <code>{@link EjbContextCloseHandlerImpl#close()}</code> method, 
	 * <code>{@link EjbContextCloseHandlerImpl#registerConsumer(Object)}</code> method, 
	 * <code>{@link EjbContextCloseHandlerImpl#removeConsumer(Object)}</code> method, 
	 * <code>{@link EjbContextCloseHandlerImpl#getConsumerCount()}</code> method, and   
	 * <code>{@link EjbContextCloseTask#run()}</code>, setters of properties of what values are quoted 
	 * in those previously stated methods, getter of properties of what value will be possibly changed in 
	 * those previously stated methods.
	 */
	/** 
	 * Defines pointcut to pick up <code>{@link EjbContextCloseHandlerImpl}</code>'s methods for handling 
	 * synchronization lock in <code>{@link #aroundExecutionOfEjbContextCloseHandlerMethod(ProceedingJoinPoint)}</code> 
	 * advise method.
	 */
	@Pointcut( value 
			= "( execution( * com.newmainsoftech.eatestutil.container" 
					+ ".EjbContextCloseHandlerImpl.getCloseTask()) " 
			+ "|| execution( * com.newmainsoftech.eatestutil.container" 
					+ ".EjbContextCloseHandlerImpl.getConsumerCount()) " 
			+ "|| (execution( !static * com.newmainsoftech.eatestutil.container" 
					+ ".EjbContextCloseHandlerImpl.*(..)) " 
				+ "&& !execution( * com.newmainsoftech.eatestutil.container" 
					+ ".EjbContextCloseHandlerImpl.get*(..)) " 
				+ "&& !execution( * com.newmainsoftech.eatestutil.container" 
						+ ".EjbContextCloseHandlerImpl.isClosedByNonConsumer())" 
				+ "&& !execution( * com.newmainsoftech.eatestutil.container" 
						+ ".EjbContextCloseHandlerImpl.isContainerClosedToo())" 
				+ "&& !execution( * com.newmainsoftech.eatestutil.container" 
						+ ".EjbContextCloseHandlerImpl.setLogger(..))) " 
			+ ") && !cflowbelow( initialization( com.newmainsoftech.eatestutil.container" 
						+ ".EjbContextCloseHandlerImpl.new( ..)))" 
			+ "&& !cflowbelow( adviceexecution())")
	public static void pointcutAtExecutionOfEjbContextCloseHandlerMethod() {}
	
	/**
	 * Defines pointcut to pick up <code>{@link EjbContextCloseHandlerImpl}</code>'s methods for handling 
	 * synchronization lock in <code>{@link #aroundExecutionOfEjbContextCloseHandlerMethod(ProceedingJoinPoint)}</code> 
	 * advise method.
	 */
	@Pointcut( value 
			= "( execution( !static * com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl" 
					+ ".EjbContextCloseTask.*(..)) " 
			+ "&& !execution( * com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl" 
					+ ".EjbContextCloseTask.get*(..))" 
			+ "&& !execution( * com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl" 
					+ ".EjbContextCloseTask.isCanceled())" 
			+ "&& !execution( * com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl" 
					+ ".EjbContextCloseTask.isContainerClosedToo())" 
			+ "&& !execution( * com.newmainsoftech.eatestutil.container.EjbContextCloseHandlerImpl" 
					+ ".EjbContextCloseTask.setLogger(..))" 
			+ ") && !cflowbelow( initialization( com.newmainsoftech.eatestutil.container" 
					+ ".EjbContextCloseHandlerImpl.EjbContextCloseTask.new( ..)))" 
			+ "&& !cflowbelow( adviceexecution())")
	public static void pointcutAtExecutionOfEjbContextCloseTaskMethod() {}
	
	public void throwProperly( 
			final Throwable throwable, final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		if ( throwable == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument cannot be null but a %2$s instance.",
							"throwable", 
							Throwable.class.getSimpleName())
					);
		}
		if ( proceedingJoinPoint == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument cannot be null but a %2$s instance.",
							"proceedingJoinPoint", 
							ProceedingJoinPoint.class.getSimpleName())
					);
		}
		
		Method method = null;
		Constructor<?> constructor = null;
			final Signature signature = proceedingJoinPoint.getSignature();
			if ( MethodSignature.class.isInstance( signature)) {
				method = ((MethodSignature)signature).getMethod();
			}
			else if ( ConstructorSignature.class.isInstance( signature)) {
				constructor = ((ConstructorSignature)signature).getConstructor();
			}
			else {
				throw new UnsupportedOperationException(
						String.format(
								"Given %1$s instance argument is neither %2$s nor %3$s type: %4$s",
								ProceedingJoinPoint.class.getSimpleName(), 
								MethodSignature.class.getSimpleName(),
								ConstructorSignature.class.getSimpleName(), 
								proceedingJoinPoint)
						);
			}
			
		if ( RuntimeException.class.isInstance( throwable)) {
			throw (RuntimeException)throwable;
		}
		else {
			if ( method != null) {
				for( Class<?> exceptionType : method.getExceptionTypes()) {
					if ( !exceptionType.isInstance( throwable)) continue;
					throw throwable;
				}
			}
			else {
				for( Class<?> exceptionType : constructor.getExceptionTypes()) {
					if ( !exceptionType.isInstance( throwable)) continue;
					throw throwable;
				}
			}
			throw new RuntimeException( throwable);
		}
	}	
	
	public Object proceed( final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		final Signature signature = proceedingJoinPoint.getSignature();
		if ( 	!MethodSignature.class.isInstance( signature) 
				&& !ConstructorSignature.class.isInstance( signature)) {
			throw new UnsupportedOperationException(
					String.format(
							"Given %1$s instance argument is neither %2$s nor %3$s type: %4$s",
							ProceedingJoinPoint.class.getSimpleName(), 
							MethodSignature.class.getSimpleName(),
							ConstructorSignature.class.getSimpleName(), 
							proceedingJoinPoint)
					);
		}
		
		Object result = null;
			try {
				if ( proceedingJoinPoint.getArgs().length < 1) {
					result = proceedingJoinPoint.proceed();
				}
				else {
					result = proceedingJoinPoint.proceed( proceedingJoinPoint.getArgs());
				}
			}
			catch( Throwable throwable) {
				throwProperly( throwable, proceedingJoinPoint);
			}
		return result;
	}
	
	/**
	 * Around advise to handle synchronization lock among <code>{@link EjbContextCloseHandlerImpl}</code>'s 
	 * methods and <code>{@link EjbContextCloseHandlerImpl}</code>'s methods.<br />
	 * 
	 * @param proceedingJoinPoint : join-point being picked up with either 
	 * <code>{@link #pointcutAtExecutionOfEjbContextCloseHandlerMethod()}</code> pointcut or 
	 * <code>{@link #pointcutAtExecutionOfEjbContextCloseTaskMethod()}</code> pointcut.
	 * @return Object being returned by proceeding join-point.
	 * @throws Throwable : checked exception being thrown by proceeding join-point or RintimeException 
	 * wrapping unchecked exception.
	 */
	@Around( value 
			= "pointcutAtExecutionOfEjbContextCloseHandlerMethod() " 
			+ "|| pointcutAtExecutionOfEjbContextCloseTaskMethod()")
	public Object aroundExecutionOfEjbContextCloseHandlerMethod( 
			final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		ReentrantLock lockForClose = null;
			Object target = proceedingJoinPoint.getTarget();
			if ( EjbContextCloseHandlerImpl.class.isInstance( target)) {
				lockForClose = ((EjbContextCloseHandlerImpl)target).getLockForClose();
			}
			else if ( EjbContextCloseTask.class.isInstance( target)) {
				lockForClose = ((EjbContextCloseTask)target).getLockForClose();
			}
			else {
				throw new IllegalMonitorStateException( 
						String.format(
								"Unexpected to cross-cut join-point in other than %1$s or %2$s type " 
								+ "instance: %3$s",
								EjbContextCloseHandlerImpl.class.getSimpleName(),
								EjbContextCloseTask.class.getSimpleName(),
								proceedingJoinPoint)
						);
			}
		if ( !lockForClose.isHeldByCurrentThread()) {
			long acquireTimeOut = -1L;
				if ( EjbContextCloseHandlerImpl.class.isInstance( target)) {
					acquireTimeOut = ((EjbContextCloseHandlerImpl)target).getCloseLockAcquireTimeOut();
				}
				else if ( EjbContextCloseTask.class.isInstance( target)) {
					acquireTimeOut = ((EjbContextCloseTask)target).getCloseLockAcquireTimeOut();
				}
			
			boolean isLocked = false;
				try {
					isLocked 
					= lockForClose.tryLock( acquireTimeOut, TimeUnit.MILLISECONDS);
				} 
				catch ( InterruptedException exception) {
					Thread.currentThread().interrupt();
				}
			if ( isLocked) {
				try {
					return proceed( proceedingJoinPoint);
				}
				finally {
					lockForClose.unlock();
				}
			}
			else {
				throw new IllegalMonitorStateException(
						String.format(
								"Within %1$d [ms], could not acquire lock to proceed \"%2$s\".",
								acquireTimeOut, 
								proceedingJoinPoint)
						);
			}
		}
		else {
			return proceed( proceedingJoinPoint);
		}
	}
}